/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

import type { LexicalEditor } from "lexical";

import * as React from "react";
import { useRef } from "react";

function clamp(value: number, min: number, max: number) {
    return Math.min(Math.max(value, min), max);
}

const Direction = {
    east: 1 << 0,
    north: 1 << 3,
    south: 1 << 1,
    west: 1 << 2
};

export function ImageResizer({
    onResizeStart,
    onResizeEnd,
    buttonRef,
    imageRef,
    maxWidth,
    editor,
    showCaption,
    setShowCaption,
    captionsEnabled
}: {
    editor: LexicalEditor;
    buttonRef: { current: null | HTMLButtonElement };
    imageRef: { current: null | HTMLElement };
    maxWidth?: number;
    onResizeEnd: (width: "inherit" | number, height: "inherit" | number) => void;
    onResizeStart: () => void;
    setShowCaption: (show: boolean) => void;
    showCaption: boolean;
    captionsEnabled: boolean;
}): JSX.Element {
    const controlWrapperRef = useRef<HTMLDivElement>(null);
    const userSelect = useRef({
        priority: "",
        value: "default"
    });
    const positioningRef = useRef<{
        currentHeight: "inherit" | number;
        currentWidth: "inherit" | number;
        direction: number;
        isResizing: boolean;
        ratio: number;
        startHeight: number;
        startWidth: number;
        startX: number;
        startY: number;
    }>({
        currentHeight: 0,
        currentWidth: 0,
        direction: 0,
        isResizing: false,
        ratio: 0,
        startHeight: 0,
        startWidth: 0,
        startX: 0,
        startY: 0
    });
    const editorRootElement = editor.getRootElement();
    // Find max width, accounting for editor padding.
    const maxWidthContainer = maxWidth
        ? maxWidth
        : editorRootElement !== null
          ? editorRootElement.getBoundingClientRect().width - 20
          : 100;
    const maxHeightContainer =
        editorRootElement !== null ? editorRootElement.getBoundingClientRect().height - 20 : 100;

    const minWidth = 100;
    const minHeight = 100;

    const setStartCursor = (direction: number) => {
        const ew = direction === Direction.east || direction === Direction.west;
        const ns = direction === Direction.north || direction === Direction.south;
        const nwse =
            (direction & Direction.north && direction & Direction.west) ||
            (direction & Direction.south && direction & Direction.east);

        const cursorDir = ew ? "ew" : ns ? "ns" : nwse ? "nwse" : "nesw";

        if (editorRootElement !== null) {
            editorRootElement.style.setProperty("cursor", `${cursorDir}-resize`, "important");
        }
        if (document.body !== null) {
            document.body.style.setProperty("cursor", `${cursorDir}-resize`, "important");
            userSelect.current.value = document.body.style.getPropertyValue("-webkit-user-select");
            userSelect.current.priority =
                document.body.style.getPropertyPriority("-webkit-user-select");
            document.body.style.setProperty("-webkit-user-select", `none`, "important");
        }
    };

    const setEndCursor = () => {
        if (editorRootElement !== null) {
            editorRootElement.style.setProperty("cursor", "text");
        }
        if (document.body !== null) {
            document.body.style.setProperty("cursor", "default");
            document.body.style.setProperty(
                "-webkit-user-select",
                userSelect.current.value,
                userSelect.current.priority
            );
        }
    };

    const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>, direction: number) => {
        if (!editor.isEditable()) {
            return;
        }

        const image = imageRef.current;
        const controlWrapper = controlWrapperRef.current;

        if (image !== null && controlWrapper !== null) {
            const { width, height } = image.getBoundingClientRect();
            const positioning = positioningRef.current;
            positioning.startWidth = width;
            positioning.startHeight = height;
            positioning.ratio = width / height;
            positioning.currentWidth = width;
            positioning.currentHeight = height;
            positioning.startX = event.clientX;
            positioning.startY = event.clientY;
            positioning.isResizing = true;
            positioning.direction = direction;

            setStartCursor(direction);
            onResizeStart();

            controlWrapper.classList.add("image-control-wrapper--resizing");
            image.style.height = `${height}px`;
            image.style.width = `${width}px`;

            document.addEventListener("pointermove", handlePointerMove);
            document.addEventListener("pointerup", handlePointerUp);
        }
    };
    const handlePointerMove = (event: PointerEvent) => {
        const image = imageRef.current;
        const positioning = positioningRef.current;

        const isHorizontal = positioning.direction & (Direction.east | Direction.west);
        const isVertical = positioning.direction & (Direction.south | Direction.north);

        if (image !== null && positioning.isResizing) {
            // Corner cursor
            if (isHorizontal && isVertical) {
                let diff = Math.floor(positioning.startX - event.clientX);
                diff = positioning.direction & Direction.east ? -diff : diff;

                const width = clamp(positioning.startWidth + diff, minWidth, maxWidthContainer);

                const height = width / positioning.ratio;
                image.style.width = `${width}px`;
                image.style.height = `${height}px`;
                positioning.currentHeight = height;
                positioning.currentWidth = width;
            } else if (isVertical) {
                let diff = Math.floor(positioning.startY - event.clientY);
                diff = positioning.direction & Direction.south ? -diff : diff;

                const height = clamp(positioning.startHeight + diff, minHeight, maxHeightContainer);

                image.style.height = `${height}px`;
                positioning.currentHeight = height;
            } else {
                let diff = Math.floor(positioning.startX - event.clientX);
                diff = positioning.direction & Direction.east ? -diff : diff;

                const width = clamp(positioning.startWidth + diff, minWidth, maxWidthContainer);

                image.style.width = `${width}px`;
                positioning.currentWidth = width;
            }
        }
    };
    const handlePointerUp = () => {
        const image = imageRef.current;
        const positioning = positioningRef.current;
        const controlWrapper = controlWrapperRef.current;
        if (image !== null && controlWrapper !== null && positioning.isResizing) {
            const width = positioning.currentWidth;
            const height = positioning.currentHeight;
            positioning.startWidth = 0;
            positioning.startHeight = 0;
            positioning.ratio = 0;
            positioning.startX = 0;
            positioning.startY = 0;
            positioning.currentWidth = 0;
            positioning.currentHeight = 0;
            positioning.isResizing = false;

            controlWrapper.classList.remove("image-control-wrapper--resizing");

            setEndCursor();
            onResizeEnd(width, height);

            document.removeEventListener("pointermove", handlePointerMove);
            document.removeEventListener("pointerup", handlePointerUp);
        }
    };
    return (
        <div ref={controlWrapperRef}>
            {!showCaption && captionsEnabled && (
                <button
                    className="image-caption-button"
                    ref={buttonRef}
                    onClick={() => {
                        setShowCaption(!showCaption);
                    }}
                >
                    Add Caption
                </button>
            )}
            <div
                className="image-resizer image-resizer-n"
                onPointerDown={event => {
                    handlePointerDown(event, Direction.north);
                }}
            />
            <div
                className="image-resizer image-resizer-ne"
                onPointerDown={event => {
                    handlePointerDown(event, Direction.north | Direction.east);
                }}
            />
            <div
                className="image-resizer image-resizer-e"
                onPointerDown={event => {
                    handlePointerDown(event, Direction.east);
                }}
            />
            <div
                className="image-resizer image-resizer-se"
                onPointerDown={event => {
                    handlePointerDown(event, Direction.south | Direction.east);
                }}
            />
            <div
                className="image-resizer image-resizer-s"
                onPointerDown={event => {
                    handlePointerDown(event, Direction.south);
                }}
            />
            <div
                className="image-resizer image-resizer-sw"
                onPointerDown={event => {
                    handlePointerDown(event, Direction.south | Direction.west);
                }}
            />
            <div
                className="image-resizer image-resizer-w"
                onPointerDown={event => {
                    handlePointerDown(event, Direction.west);
                }}
            />
            <div
                className="image-resizer image-resizer-nw"
                onPointerDown={event => {
                    handlePointerDown(event, Direction.north | Direction.west);
                }}
            />
        </div>
    );
}
